package com.puff.ioc;

import java.util.concurrent.ConcurrentMap;

import com.puff.exception.ExceptionUtil;
import com.puff.framework.annotation.BeanScope;
import com.puff.framework.utils.ClassUtil;
import com.puff.framework.utils.StringUtil;
import com.puff.framework.utils.jdk.ConcurrentHashMapV8;
import com.puff.ioc.info.BeanInfo;
import com.puff.ioc.info.Property;
import com.puff.ioc.loader.AnnotationLoader;
import com.puff.ioc.loader.Loader;
import com.puff.jdbc.tx.proxy.TXProxy;
import com.puff.plugin.spring.BeanContextHelper;

public class BeanFactory {
	private static final ConcurrentMap<String, BeanInfo> beans = new ConcurrentHashMapV8<String, BeanInfo>();
	private static boolean springSupport = true;
	static {
		try {
			Class.forName("org.springframework.context.ApplicationContext");
		} catch (Exception e) {
			springSupport = false;
		}
	}

	public static void putBean(BeanInfo info) {
		String id = StringUtil.firstCharToLowerCase(info.getId());
		if (!beans.containsKey(id)) {
			beans.put(id, info);
		}
	}

	public synchronized static void loadPackage(String packageName) {
		Loader loader = new AnnotationLoader();
		loader.load(packageName);
	}

	@SuppressWarnings("unchecked")
	public static <T> T getBean(String id) {
		Object object = null;
		try {
			object = get(id);
		} catch (Exception e) {
			ExceptionUtil.throwRuntime(e);
		}
		return (T) object;
	}

	public static <T> T getBean(Class<T> klass) {
		String simpleName = klass.getSimpleName();
		return getBean(StringUtil.firstCharToLowerCase(simpleName));
	}

	private static Object get(String id) throws Exception {
		Object instance = null;
		BeanInfo info = beans.get(id);
		if (info != null) {
			if (BeanScope.SINGLETON.equals(info.getScope())) {
				instance = info.getInstance();
				if (instance == null) {
					synchronized (id) {
						instance = info.getInstance();
						if (instance == null) {
							instance = newInstance(id, info);
							info.setInstance(instance);
						}
					}
				}
			} else {
				instance = newInstance(id, info);
			}
			for (Property prop : info.getProperties()) {
				String pname = prop.getName();
				info = beans.get(prop.getValue());
				Object val = null;
				if (BeanScope.SINGLETON.equals(info.getScope())) {
					val = info.getInstance();
					if (val == null) {
						val = get(prop.getValue());
					}
				} else {
					val = newInstance(pname, info);
				}
				ClassUtil.inject(instance, pname, val, prop);
			}
		} else if (springSupport) {
			return BeanContextHelper.INSTANCE.getBean(id);
		}
		return instance;
	}

	private static Object newInstance(String id, BeanInfo info) {
		Object instance;
		if (info.isTransaction()) {
			TXProxy proxy = TXProxyFactory.getProxy(info.getClazz());
			instance = proxy.getProxy();
		} else {
			instance = ClassUtil.newInstance(info.getClassName());
		}
		if (instance == null) {
			throw new RuntimeException(String.format("Can not instantiate id=%s, class=%s", id, info.getClassName()));
		}
		return instance;
	}

}